#include <retro.h>
#include "retro.creator.h"

using namespace Retro::Creator::Editor;
using namespace SPLib::Geom;

#pragma region Macros
#define TILE_X            mapTileSize
#define TILE_Y            mapTileSize
#define TILE_POINT        Point::get(TILE_X, TILE_Y)
#define TILE_SIZE         Size::get(TILE_X, TILE_Y)
#define TILE_BOUNDS       Bounds::get(TILE_X, TILE_Y, TILE_X, TILE_Y)
#define SELECT_BOUNDS     (Frame::get()->getSelector()->getSelection())
#define MAP_BOUNDS        Bounds::get(0, 0, mMapWidth * TILE_X, mMapHeight * TILE_Y)
#define MAP_SIZE          Size::get(mMapWidth * TILE_X, mMapHeight * TILE_Y)

#define ISCELL(i)         ((i) >= 0 && (i) < (mMapWidth * mMapHeight))
#define CELLFROMGRID(pt)  ((pt).x + (pt).y * mMapWidth)
#define CELLFROMPOINT(pt) ((pt).x / TILE_X + (pt).y / TILE_Y * mMapWidth)
#define GRIDMAP           ((Data::GridMap*)getMap())
#pragma endregion

// CREATE & DESTROY -----------------------------------------------------------

GridEditor::GridEditor() :
  MapEditor(Data::ResourceGridMap)
{
  tileset       = NULL;
  mVisitMap     = NULL;
  mFillMap      = NULL;
  pCopy         = NULL;
  pCurrentUndo  = NULL;
  pUndo         = NULL;
  mapBuffer     = NULL;
  mFontRes      = NULL;
  mTilesetRes   = NULL;

  mSnapSelection   = TRUE;
  mShowIndices  = FALSE;

  mapTileSize   = 0;

  mSelection.setEmpty();
}

// RENDERING ------------------------------------------------------------------

VOID    GridEditor::getGridView(Geom::Rect &value)
{
  value.left    = mView.x / TILE_X;
  value.top     = mView.y / TILE_Y;
  value.right   = (LONG)ceilf(((REAL)mView.x + mView.cx) / TILE_X);
  value.bottom  = (LONG)ceilf(((REAL)mView.y + mView.cy) / TILE_Y);
  value.right   = min(mMapWidth, value.right);
  value.bottom  = min(mMapHeight, value.bottom);
}

VOID    GridEditor::drawLayer(INT layer)
{
  if (!mTilesetRes) return;

  Rect gridView;
  getGridView(gridView);

  getDevice()->beginBatch(mTilesetRes);

  // draw tiles
  for (int x = gridView.left; x < gridView.right; ++x)
  for (int y = gridView.top; y < gridView.bottom; ++y)
  {
    int texture = getDisplayTexture(x, y, layer);

    if (texture >= 0)
    {
      getDevice()->addBatch(
        Point::get(
        x * TILE_X - mView.x,
        y * TILE_Y - mView.y),
        &Bounds::get(
        (texture * mapTileSize) % mTilesetRes->getWidth(),
        (texture * mapTileSize) / mTilesetRes->getWidth() * mapTileSize,
        mapTileSize,
        mapTileSize),
        0xFFFFFFFF);
    }
  }

  getDevice()->end();
}

VOID    GridEditor::drawIndices()
{
  Rect gridView;
  getGridView(gridView);

  getDevice()->beginFont();

  for (int x = gridView.left; x < gridView.right; ++x)
  for (int y = gridView.top; y < gridView.bottom; ++y)
  {
    int i = x + y * mMapWidth;

    if (mapBuffer[mLayer][i].texture >= 0)
    {
      TCHAR szIndex[100];
      _itot(mapBuffer[mLayer][i].texture + 1, szIndex, 10);

      getDevice()->drawString(
        mFontRes, szIndex,
        DT_NOCLIP | DT_CENTER | DT_VCENTER, getForeColor(),
        Bounds::get(x * TILE_X - mView.x, y * TILE_Y - mView.y, TILE_X, TILE_Y));
    }
  }

  getDevice()->end();
}

VOID    GridEditor::drawForeground()
{
  MapEditor::drawForeground();

  // draw overlay
  if (getShowIndices()) drawIndices();

  // draw selection bounds
  if (mSelectMode != SelectModeNone || getTool() == GridToolSelect)
  {
    Bounds select = mSelection;

    if (mSnapSelection)
      Drawing::snapBounds(select, TILE_X, TILE_Y);
    if (doScale())
      Drawing::scaleBounds(select, MAP_SIZE);

    select.absolute();

    if (!select.isEmpty())
    {
      select = select.point + select.size - mView.point;

      if (mSelectMode == SelectModeCopy || getTool() == GridToolSelect)
      {
        getDevice()->beginTexture();
        getDevice()->fillRectangle(select, 0x3FFFFFFF);
        getDevice()->end();
      }

      getDevice()->beginLine(1.0f, 0xF0F0F0F0);
      getDevice()->drawRectangle(select + Bounds::get(1, 1, -2, -2), 0xFF000000);
      getDevice()->drawRectangle(select, 0xFFFFFFFF);
      getDevice()->end();

      if (mSelectMode == SelectModeCopy)
      {
        getDevice()->beginFont();
        getDevice()->drawString(
          mFontRes,
          _T("Copy"),
          DT_NOCLIP | DT_CENTER | DT_VCENTER,
          0x7F000000,
          select);
        getDevice()->end();
      }
    }
  }
}

// RESOURCES ------------------------------------------------------------------

VOID    GridEditor::onUninitializeDisplay()
{
  getDevice()->getBuffer().clear();

  mFontRes    = NULL;
  mTilesetRes = NULL;
  tileset     = NULL;
}

VOID    GridEditor::onPrepareDisplay()
{
  // retrieve getMap() tileset
  if (tileset = (Data::Texture*)Frame::get()->getProject().findResource(
    Data::ResourceTexture, GRIDMAP->getTilesetID()))
  {
    mapTileSize = tileset->getDataTileset().tileSize;
    setGridSize(mapTileSize);
  }
  else
  {
    mapTileSize = RETRO_GRIDMAP_DEFTILESIZE;
  }

  getDevice()->setWidth(mMapWidth * TILE_X);
  getDevice()->setHeight(mMapHeight * TILE_Y);
}

VOID    GridEditor::onInitializeDisplay()
{
  mFontRes = getDevice()->createFont(_T("Arial"), 20, FW_BOLD, FALSE);

  if (tileset)
  {
    mTilesetRes = (Device::Display::Texture*)
      getDevice()->createTexture(tileset->getPath());
    mTilesetRes->setResource(tileset);
  }
}

VOID    GridEditor::initToolbar()
{
  // create tool menu
  getToolMenu().clear();
  getToolMenu().append(GridToolPen,    _T("Pen"));
  getToolMenu().append(GridToolStamp,  _T("Stamp"));
  getToolMenu().append(GridToolRect,   _T("Rect"));
  getToolMenu().append(GridToolFill,   _T("Fill"));
  getToolMenu().append(GridToolSelect, _T("Select"));

  // toolbar items
  ToolItem items[] = {
    {MapItemToggleGrid,     _T("Grid"),     ToolItemButton},
    {MapItemToggleIndices,  _T("Indices"),  ToolItemButton},
    {0,                     _T(""),         ToolItemBreak},
    {0,                     _T("Edit"),     ToolItemLabel},
    {MapItemLayer,          _T(""),         ToolItemMenu,
    menuLayers},
    {MapItemDisplay,        _T(""),         ToolItemMenu,
    menuDisplay,     0, TOOLITEM_AUTORENAME},
    {0,                     _T(""),         ToolItemBreak},
    {0,                     _T("With"),     ToolItemLabel},
    {MapItemTool,           _T(""),         ToolItemMenu,
    getToolMenu(),   0, TOOLITEM_AUTORENAME},
    {0,                     _T("At"),       ToolItemLabel},
    {EditorItemZoom,        _T(""),         ToolItemMenu,
    getMenuZoom()},
  };

  getToolBar()->addItems(items, sizeof(items) / sizeof(ToolItem));
  getToolBar()->setItemChecked(MapItemToggleGrid, mShowGrid);
  getToolBar()->setItemChecked(MapItemToggleIndices, mShowIndices);

  setTool(GridToolPen);

  MapEditor::initToolbar();
}

VOID    GridEditor::onToolbar(ToolBarNotify* iData)
{
  switch (iData->itemID)
  {
  case MapItemToggleIndices:
    setShowIndices(!getShowIndices());
    break;
  default:
    MapEditor::onToolbar(iData);
    break;
  }
}

// USER INPUT -----------------------------------------------------------------

VOID    GridEditor::scrollToGridPoint(const Geom::Point& pt)
{
  Frame::get()->getSurface()->scrollToPoint(
    (pt - mView.point) * getZoom() / 100);
}

VOID    GridEditor::onMouseDown(Event::MouseEvent& iEvent)
{
  if (!getMap()) return;

  // focus window
  focus();
  // set snap selection flag
  mSnapSelection = doSnap();

  // adjust mouse position
  adjustMousePos(iEvent.point);

  if (iEvent.mkButton & MK_RBUTTON)
  {
    if (mSelectMode == SelectModeNone)
    {
      mSelection  = Size::get(0, 0) + iEvent.point;

      if (getTool() != GridToolSelect)
      {
        // begin copy
        mSelectMode = SelectModeCopy;
        sendNotify(EditorNotifyCopy);
      }
    }
    else
    {
      // cancel getTool()
      mSelectMode = SelectModeNone;
    }

    // redraw getDevice()
    invalidate(FALSE);
  }
  else if (iEvent.mkButton & MK_LBUTTON)
  {
    if (mSelection.contains(iEvent.point) &&
      getTool() == GridToolSelect)
    {
      // drag selection
      mSelectMode  = SelectModeDrag;
      mDragPoint   = iEvent.point;
      mSelectPoint = mSelection.point;

      // copy selection
      addUndo();
      copySelection();

      // clear selection if control not pressed
      if (!(GetKeyState(VK_CONTROL) >> 1))
        fillSelection(-1);
    }
    else
    {
      // begin selection
      mSelection   = iEvent.point + Size::get(0, 0);
      mSelectMode  = SelectModeSelect;
      mSelectPoint = mSelection.point;

      switch (getTool())
      {
      case GridToolStamp:
        addUndo();
        useSelection();
        break;
      case GridToolPen:
        addUndo();
        usePen();
        break;
      case GridToolFill:
        addUndo();
        useFill();
        break;
      }

      invalidate();
    }
  }
}

VOID    GridEditor::onMouseUp(Event::MouseEvent& iEvent)
{
  if (!getMap()) return;

  if ((iEvent.mkButton & MK_LBUTTON) != MK_LBUTTON ||
    ((iEvent.mkButton & MK_RBUTTON) != MK_RBUTTON &&
    mSelectMode == SelectModeCopy))
  {
    mSelection.absolute();
    adjustMousePos(iEvent.point);

    if (doScale())
      Drawing::scaleBounds(mSelection, MAP_SIZE);
    if (mSnapSelection)
      Drawing::snapBounds(mSelection, TILE_X, TILE_Y);

    switch (mSelectMode)
    {
    case SelectModeCopy:
      copySelection();
      copyOffset = (iEvent.point - mSelection.point) / TILE_POINT;
      break;
    case SelectModeDrag:
      if (mSelection.point / TILE_POINT == mSelectPoint / TILE_POINT)
        removeUndo();
      else
        pasteSelection();
      break;
    case SelectModeSelect:
      switch (getTool())
      {
      case GridToolRect:
        addUndo();
        useSelection();
      }

      if (getTool() != GridToolSelect)
      {
        if (pCurrentUndo->data)
        {
          getMap()->setUnsaved();
          sendNotify(EditorNotifyEdit);
        }
        else
        {
          removeUndo();
        }
      }
      break;
    }

    mSelectMode = SelectModeNone;
    invalidate();
  }
}

VOID    GridEditor::onMouseMove(Event::MouseEvent& iEvent)
{
  if (!getTool()) return;

  if (mSelectMode != SelectModeNone)
  {
    Frame::get()->getSurface()->scrollToPoint(iEvent.point);

    mSnapSelection = doSnap();
    adjustMousePos(iEvent.point);
  }

  switch (mSelectMode)
  {
  case SelectModeCopy:
    mSelection.size = Size(iEvent.point - mSelection.point);
    invalidate(FALSE);
    break;
  case SelectModeSelect:
    switch (getTool())
    {
    case GridToolPen:
      mSelection.point = iEvent.point;
      usePen();
      break;
    case GridToolStamp:
      mSelection.point = iEvent.point;
      mSelectPoint = iEvent.point;
      useSelection();
      break;
    case GridToolFill:
      mSelection.point = iEvent.point;
      useFill();
      break;
    default:
      mSelection.size = Size(iEvent.point - mSelection.point);
      break;
    }

    invalidate(FALSE);
    break;
  case SelectModeDrag:
    mSelection.point = mSelectPoint + (iEvent.point - mDragPoint);

    if (mSnapSelection)
      mSelection -= mSelection.point % Point::get(TILE_X, TILE_Y);

    invalidate(FALSE);
    break;
  }
}

VOID    GridEditor::onKeyDown(Event::KeyEvent& iEvent)
{
  if (!getMap()) return;

  if (getTool() == GridToolSelect)
  {
    switch (iEvent.vkCode)
    {
    case VK_BACK:
    case VK_DELETE:
      addUndo();
      fillSelection(-1);
      invalidate(FALSE);
      return;
    case VK_LEFT:
      mSelection.x = max(mSelection.x - TILE_X, 0);
      scrollToGridPoint(mSelection.point);
      invalidate(FALSE);
      return;
    case VK_UP:
      mSelection.y = max(mSelection.y - TILE_Y, 0);
      scrollToGridPoint(mSelection.point);
      invalidate(FALSE);
      return;
    case VK_RIGHT:
      mSelection.x = min(
        mSelection.x + TILE_X,
        mMapWidth * TILE_X - mSelection.cx);
      scrollToGridPoint(Point::get(
        mSelection.x + mSelection.cx,
        mSelection.y));
      invalidate(FALSE);
      return;
    case VK_DOWN:
      mSelection.y = min(
        mSelection.y + TILE_Y,
        mMapHeight * TILE_Y - mSelection.cy);
      scrollToGridPoint(Point::get(
        mSelection.x,
        mSelection.y + mSelection.cy));
      invalidate(FALSE);
      return;
    }
  }

  MapEditor::onKeyDown(iEvent);
}

// GETTERS & SETTERS ----------------------------------------------------------

INT     GridEditor::getGridTexture(INT x, INT y, INT iLayer)
{
  if (x < 0 || y < 0 || x >= mMapWidth || y >= mMapHeight) return -1;

  return mapBuffer[iLayer][x + y * mMapWidth].texture;
}

VOID    GridEditor::setGridTexture(INT x, INT y, INT iLayer, INT texture)
{
  if (x < 0 || y < 0 || x >= mMapWidth || y >= mMapHeight || texture < -1) return;

  mapBuffer[iLayer][x + y * mMapWidth].texture = texture;
  invalidate(FALSE);
}

Bounds  GridEditor::getGridSelection()
{
  Bounds select = mSelection;

  select.absolute();
  Drawing::snapBounds(select, TILE_X, TILE_Y);

  return select / Size::get(TILE_X, TILE_Y) / Point::get(TILE_X, TILE_Y);
}

VOID    GridEditor::setGridSelection(const Geom::Bounds& value)
{
  mSelection = value;
  mSelection.absolute();

  mSelection *= Geom::Point::get(TILE_X, TILE_Y);
  mSelection *= Geom::Size::get(TILE_X, TILE_Y);

  invalidate(FALSE);
}

VOID    GridEditor::setPixelSelection(const Geom::Bounds& value)
{
  mSelection = value;
  invalidate(FALSE);
}

INT     GridEditor::getSelectedTexture(INT x, INT y)
{
  if (!mTilesetRes)
    return -1;

  if (Frame::get()->getSelector()->getTileErase() && !pCopy)
    return -1;

  Geom::Bounds select;
  int texture;

  if (pCopy)
  {
    select = Point::get(0, 0) + copySize;

    x += copyOffset.x;
    y += copyOffset.y;
  }
  else
  {
    select = SELECT_BOUNDS / TILE_BOUNDS;
  }

  if (select.cx == 0 || select.cy == 0)
    return -1;

  INT tx = x - mSelectPoint.x / TILE_X;
  INT ty = y - mSelectPoint.y / TILE_Y;

  while (tx < 0) tx += select.cx;
  while (ty < 0) ty += select.cy;

  tx = tx % select.cx + select.x;
  ty = ty % select.cy + select.y;

  if (pCopy)
    texture = pCopy[tx + ty * copySize.cx].texture;
  else
    texture = tx + ty * mTilesetRes->getWidth() / TILE_X;

  return max(-1, texture);
}

INT     GridEditor::getDisplayTexture(INT x, INT y, INT layer)
{
  if (layer != layer ||
    ((mSelectMode != SelectModeSelect ||
    getTool() != GridToolRect) && mSelectMode != SelectModeDrag))
    return mapBuffer[layer][x + y * mMapWidth].texture;

  Geom::Bounds gridSelect = mSelection;

  Drawing::snapBounds(gridSelect, TILE_X, TILE_Y);
  if (doScale()) Drawing::scaleBounds(gridSelect, MAP_SIZE);

  gridSelect.absolute();
  gridSelect = gridSelect / TILE_BOUNDS;

  if (gridSelect.contains(Point::get(x, y)) && !gridSelect.isEmpty())
  {
    if (getTool() == GridToolSelect)
    {
      return pCopy[x - gridSelect.x +
        (y - gridSelect.y) * copySize.cx].texture;
    }
    else
    {
      return getSelectedTexture(x, y);
    }
  }
  else
  {
    return mapBuffer[layer][x + y * mMapWidth].texture;
  }
}

VOID    GridEditor::setTool(MapTool value)
{
  if (getTool() != value)
  {
    if (getMap())
      mSelection = Bounds::get(0, 0, TILE_X, TILE_Y);
    else
      mSelection.setEmpty();

    clearCopy();
    invalidate(FALSE);
  }

  MapEditor::setTool(value);
}

VOID    GridEditor::setShowIndices(BOOL value)
{
  mShowIndices = value;

  if (getToolBar())
    getToolBar()->setItemChecked(MapItemToggleIndices, value);

  sendNotify(GridNotifyIndices);
  invalidate(FALSE);
}

// UNDO BUFFER ----------------------------------------------------------------

VOID    GridEditor::clearUndo(UndoState* from)
{
  if (!from) return;

  if (from->prev)
    from->prev->next = NULL;

  if (from == pCurrentUndo)
    pCurrentUndo = pUndo = NULL;

  while (from)
  {
    UndoState* nextState = from->next;

    while (from->data)
    {
      UndoData* nextData = from->data->next;

      delete[] from->data->cells;
      delete from->data;

      from->data = nextData;
    }

    delete from;
    from = nextState;
  }
}

VOID    GridEditor::removeUndo()
{
  undo();

  if (pCurrentUndo)
  {
    if (pCurrentUndo->next)
      clearUndo(pCurrentUndo->next);
  }
  else
  {
    clearUndo(pUndo);
    pUndo = NULL;
  }

  sendNotify(EditorNotifyCancel);
}

VOID    GridEditor::addUndo()
{
  if (pCurrentUndo)
  {
    clearUndo(pCurrentUndo->next);

    pCurrentUndo->next = new UndoState;
    pCurrentUndo->next->prev = pCurrentUndo;
    pCurrentUndo = pCurrentUndo->next;
  }
  else
  {
    pUndo = pCurrentUndo = new UndoState;
    pUndo->prev = NULL;
  }

  pCurrentUndo->data      = NULL;
  pCurrentUndo->lastData  = NULL;
  pCurrentUndo->next      = NULL;
  pCurrentUndo->tool      = getTool();
  pCurrentUndo->mapSize   = Geom::Size::get(mMapWidth, mMapHeight);
  pCurrentUndo->mapLayers = mMapLayers;
  pCurrentUndo->layer     = mLayer;
}

GridEditor::UndoData* GridEditor::addUndoData()
{
  if (pCurrentUndo->lastData)
  {
    pCurrentUndo->lastData->next = new UndoData;
    pCurrentUndo->lastData = pCurrentUndo->lastData->next;
  }
  else
  {
    pCurrentUndo->lastData = pCurrentUndo->data = new UndoData;
  }

  pCurrentUndo->lastData->next = NULL;
  return pCurrentUndo->lastData;
}

VOID    GridEditor::applyUndoBuffer(UndoState* state)
{
  if (!pCurrentUndo) return;

  while (state)
  {
    if (state->layer < mMapLayers)
    {
      UndoData* data = state->data;

      while (data) {
        int maxW = min(data->bounds.cx, mMapWidth - data->bounds.x);
        int maxH = min(data->bounds.cy, mMapHeight - data->bounds.y);

        if (maxW > 0 && maxH > 0)
        {
          for (int y = 0; y < maxH; ++y)
          {
            memcpy(
              &mapBuffer[state->layer][
                (y + data->bounds.y) * mMapWidth +
                  data->bounds.x],
                  &data->cells[y * data->bounds.cx],
                  sizeof(Data::GridCell) * maxW);
          }
        }

        data = data->next;
      }
    }

    if (state == pCurrentUndo) break;
    state = state->next;
  }
}

VOID    GridEditor::flush(Data::Resource* oResource)
{
  Data::GridMap* wMap = (Data::GridMap*)oResource;

  wMap->setSize(mMapWidth, mMapHeight, mMapLayers);

  for (int i = 0; i < mMapLayers; ++i)
  {
    memcpy(wMap->getGrid()[i], mapBuffer[i],
      sizeof(Data::GridCell) * mMapWidth * mMapHeight);
  }
}

VOID    GridEditor::fillMapBuffer()
{
  int maxW = min(getMap()->getWidth(), mMapWidth);
  int maxH = min(getMap()->getHeight(), mMapHeight);
  int maxL = min(getMap()->getLayers(), mMapLayers);

  for (int l = 0; l < mMapLayers; ++l)
  {
    memset(mapBuffer[l], -1, sizeof(Data::GridCell) * mMapWidth * mMapHeight);

    if (l < maxL)
    {
      for (int y = 0; y < maxH; ++y)
      {
        memcpy(
          &mapBuffer[l][y * mMapWidth],
          &GRIDMAP->getGrid()[l][y * getMap()->getWidth()],
          sizeof(Data::GridCell) * maxW);
      }
    }
  }

  applyUndoBuffer(pUndo);
}

VOID    GridEditor::freeMapBuffer()
{
  // free map buffer
  if (mapBuffer)
  {
    for (int l = 0; l < mMapLayers; ++l)
      delete[] mapBuffer[l];

    delete[] mapBuffer;
    mapBuffer = NULL;
  }

  // free visit and fill map
  delete[] mVisitMap;
  delete[] mFillMap;
  mVisitMap = NULL;
}

VOID    GridEditor::createMapBuffer()
{
  // allocate map buffer
  mapBuffer = new Data::GridCell*[mMapLayers];

  // allocate map buffer lines
  for (int i = 0; i < mMapLayers; ++i)
    mapBuffer[i] = new Data::GridCell[mMapWidth * mMapHeight];

  // create visit and fill maps
  mVisitMap = new BOOL[mMapWidth * mMapHeight];
  mFillMap  = new INT[mMapWidth * mMapHeight];

  // fill map buffer
  fillMapBuffer();
}

BOOL    GridEditor::undo()
{
  if (pCurrentUndo)
  {
    getMap()->setUnsaved();
    pCurrentUndo = pCurrentUndo->prev;

    updateSize();
    fillMapBuffer();
    invalidate(FALSE);

    sendNotify(EditorNotifyUndo);
    return TRUE;
  }

  return FALSE;
}

BOOL    GridEditor::redo()
{
  if (pCurrentUndo)
  {
    if (pCurrentUndo->next)
      pCurrentUndo = pCurrentUndo->next;
    else
      return FALSE;
  }
  else if (pUndo)
  {
    pCurrentUndo = pUndo;
  }
  else
  {
    return FALSE;
  }

  updateSize();
  applyUndoBuffer(pCurrentUndo);

  getMap()->setUnsaved();
  invalidate(FALSE);

  sendNotify(EditorNotifyRedo);
  return TRUE;
}

BOOL    GridEditor::save(HANDLE iFile)
{
  Data::GridMap wMap;

  memcpy(&wMap, GRIDMAP, sizeof(wMap));
  wMap.pGrid = NULL;

  flush(&wMap);

  BOOL wResult = wMap.save(iFile);

  wMap.free();
  return wResult;
}

VOID    GridEditor::setBufferSize(INT cx, INT cy, INT layers)
{
  if (mMapWidth != cx || mMapHeight != cy || mMapLayers != layers)
  {
    addUndo();

    pCurrentUndo->mapSize.cx = cx;
    pCurrentUndo->mapSize.cy = cy;
    pCurrentUndo->mapLayers  = layers;
    pCurrentUndo->tool       = MapToolNone;

    updateSize();
  }
}

VOID    GridEditor::setTileset(INT iTilesetID)
{
  onUninitializeDisplay();

  GRIDMAP->setTilesetID(iTilesetID);

  onPrepareDisplay();
  getDevice()->initialize();
  onInitializeDisplay();
}

VOID    GridEditor::updateSize()
{
  Geom::Size newSize = pCurrentUndo ?
    pCurrentUndo->mapSize :
    Geom::Size::get(getMap()->getWidth(), getMap()->getHeight());

  INT newLayers = pCurrentUndo ?
    pCurrentUndo->mapLayers :
  getMap()->getLayers();

  if (newSize.cx == mMapWidth &&
    newSize.cy == mMapHeight &&
    newLayers == mMapLayers)
    return;

  onUninitializeDisplay();
  freeMapBuffer();

  mMapLayers   = newLayers;
  mMapWidth    = newSize.cx;
  mMapHeight   = newSize.cy;

  onPrepareDisplay();
  getDevice()->initialize();
  onInitializeDisplay();

  createMapBuffer();
  updateLayers();
  Frame::get()->getSurface()->updateScroll();
}

// PAINTING TOOLS -------------------------------------------------------------

VOID    GridEditor::copySelection()
{
  Bounds gridSelect = mSelection /
    Bounds::get(TILE_X, TILE_Y, TILE_X, TILE_Y);

  delete[] pCopy;
  pCopy = new Data::GridCell[gridSelect.cx * gridSelect.cy];

  for (int x = 0; x < gridSelect.cx; ++x)
    for (int y = 0; y < gridSelect.cy; ++y)
    {
      int i = gridSelect.x + x + (gridSelect.y + y) * mMapWidth;

      pCopy[x + y * gridSelect.cx].texture =
        !ISCELL(i) ? -1 : mapBuffer[mLayer][i].texture;
    }

    copyOffset.setEmpty();
    copySize = gridSelect.size;
}

VOID    GridEditor::pasteSelection()
{
  addUndoData();
  UndoData* data  = pCurrentUndo->lastData;

  data->bounds    = mSelection / TILE_BOUNDS;
  data->cells     = new Data::GridCell[data->bounds.cx * data->bounds.cy];
  data->next      = NULL;

  for (int x = 0; x < data->bounds.cx; ++x)
    for (int y = 0; y < data->bounds.cy; ++y)
    {
      int i = data->bounds.x + x + (data->bounds.y + y) * mMapWidth;

      if (ISCELL(i))
        data->cells[x + y * data->bounds.cx] =
        pCopy[x + y * data->bounds.cx];
    }

    applyUndoBuffer(pCurrentUndo);
}

VOID    GridEditor::usePen()
{
  Point gridPt = mSelection.point / TILE_POINT;
  INT texture  = getSelectedTexture(gridPt.x, gridPt.y);

  if (mapBuffer[mLayer][CELLFROMGRID(gridPt)].texture != texture)
  {
    addUndoData();
    UndoData* data = pCurrentUndo->lastData;

    data->bounds         = gridPt + Size::get(1, 1);
    data->cells          = new Data::GridCell;
    data->cells->texture = texture;

    mapBuffer[mLayer][CELLFROMGRID(gridPt)].texture = texture;
  }
}

VOID    GridEditor::useSelection()
{
  addUndoData();
  UndoData* data  = pCurrentUndo->lastData;

  data->bounds    = mSelection / TILE_BOUNDS;

  if (getTool() == GridToolStamp)
    data->bounds.size = pCopy ? copySize : (SELECT_BOUNDS / TILE_SIZE);

  data->cells     = new Data::GridCell[data->bounds.cx * data->bounds.cy];
  data->next      = NULL;

  data->bounds.cx = min(data->bounds.cx, mMapWidth - data->bounds.x);
  data->bounds.cy = min(data->bounds.cy, mMapHeight - data->bounds.y);

  for (int x = 0; x < data->bounds.cx; ++x)
    for (int y = 0; y < data->bounds.cy; ++y)
    {
      int i = x + data->bounds.x +
        (y + data->bounds.y) * mMapWidth;

      if (ISCELL(i))
      {
        int t = getSelectedTexture(
          x + data->bounds.x, y + data->bounds.y);

        data->cells[x + y * data->bounds.cx].texture = t;
        mapBuffer[mLayer][i].texture = t;
      }
    }
}

VOID    GridEditor::useFill()
{
  Point origin    = mSelection.point / TILE_POINT;
  INT wFillCount  = 0;
  INT wOldTexture = mapBuffer[mLayer][origin.x + origin.y * mMapWidth].texture;

  memset(mVisitMap, FALSE, sizeof(BOOL) * mMapWidth * mMapHeight);
  mFillMap[wFillCount++] = origin.x + origin.y * mMapWidth;

  do
  {
    INT wGridIndex = mFillMap[--wFillCount];
    Geom::Point wPos = { wGridIndex % mMapWidth, wGridIndex / mMapWidth };

    UndoData* wUndo = addUndoData();

    wUndo->bounds         = wPos + Size::get(1, 1);
    wUndo->cells          = new Data::GridCell;
    wUndo->cells->texture = getSelectedTexture(wPos.x, wPos.y);

    mapBuffer[mLayer][wGridIndex].texture = wUndo->cells->texture;
    mVisitMap[wGridIndex] = TRUE;

    addToFillList(wPos.x - 1, wPos.y, wOldTexture, wFillCount);
    addToFillList(wPos.x + 1, wPos.y, wOldTexture, wFillCount);
    addToFillList(wPos.x, wPos.y - 1, wOldTexture, wFillCount);
    addToFillList(wPos.x, wPos.y + 1, wOldTexture, wFillCount);
  }
  while (wFillCount > 0);

  invalidate(FALSE);
}

VOID    GridEditor::addToFillList(
  INT iX,
  INT iY,
  INT iOldTexture,
  INT& ioFillCount
  )
{
  if (iX < 0 || iY < 0 || iX >= mMapWidth || iY >= mMapHeight)
    return;

  if (mapBuffer[mLayer][iX + iY * mMapWidth].texture == iOldTexture &&
    !mVisitMap[iX + iY * mMapWidth])
  {
    mFillMap[ioFillCount++] = iX + iY * mMapWidth;
  }
}

VOID    GridEditor::fillSelection(INT texture)
{
  addUndoData();
  UndoData* data  = pCurrentUndo->lastData;

  data->bounds = mSelection;
  data->bounds.absolute();
  Drawing::snapBounds(data->bounds, TILE_X, TILE_Y);
  if (doScale()) Drawing::scaleBounds(data->bounds, MAP_SIZE);
  data->bounds /= TILE_BOUNDS;

  data->cells = new Data::GridCell[data->bounds.cx * data->bounds.cy];

  memset(data->cells, texture,
    sizeof(Data::GridCell) * data->bounds.cx * data->bounds.cy);

  applyUndoBuffer(pCurrentUndo);
}
